#define _GNU_SOURCE
#include <sys/types.h>
#include <string.h>
#include <time.h>
#include <sched.h>
#include <sys/shm.h>
#include <sys/prctl.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include "sbdns.h"

extern int sb_ncpu;

int msg_create ()
{
	return msgget(IPC_PRIVATE, 0666);
}

int msg_remove(int msg_id)
{
	return msgctl(msg_id, IPC_RMID, NULL);
}

int msg_send (int id, struct dns_msg *msg, int nowaitflag)
{
	int msgflg; 

	msgflg = nowaitflag ? IPC_NOWAIT : 0;
	if (msgsnd (id, (void *) &msg->m, msg->mtext_len, IPC_NOWAIT))
	{
		return SB_ERROR;
	}

	return SB_OK;
}


int msg_recv (int id, long msgtyp, struct dns_msg *msg, int nowaitflag)
{
	int mtext_len = 0;
	int msgflg; 

	msgflg = nowaitflag ? IPC_NOWAIT : 0;
	if ((mtext_len = msgrcv(id, (void *) &msg->m, SB_MSG_QUEUE_MAX_MSG_LEN, msgtyp, msgflg)) == -1)
	{
		return SB_ERROR;
	}

	msg->mtext_len = mtext_len;
	return SB_OK;
}

int shm_create(int shm_size)
{
	return shmget(IPC_PRIVATE, shm_size, 0666);
}

int shm_remove(int shm_id)
{
	return shmctl(shm_id, IPC_RMID, NULL);
}

void* shm_attach(int shm_id)
{
	return shmat(shm_id, NULL, 0);
}

int shm_detach(const void *shmaddr)
{
	return shmdt(shmaddr);
}

int shm_ctl(int shm_id, struct shmid_ds *buf)
{
	return shmctl(shm_id, IPC_SET, buf);
}

char sb_log_buf[SB_LOG_BUF_LEN + 1];
int sb_log_level = DEBUG;
FILE *sb_log_file = NULL;
extern char cache_str_time[sizeof("2014-12-12 12:12:12")];
extern const int cache_str_time_len;

FILE* SetLogFile(FILE *file)
{
	if (sb_log_file == file)
		return NULL;

	FILE *old_file = sb_log_file;
	sb_log_file = file;	

	return old_file;
}

int SetLogLevel(int level)
{
	sb_log_level = level;
}

void GenerateLogMsg(char *filename, int lineno, int log_level)
{
	static char *debug_str =        "[debug]";
	static char *info_str =         "[info ]";
	static char *error_str =        "[error]";
	static char *fatal_str =        "[fatal]";
	static char *system_str =       "[system]";
	int len = 0;

	memset(sb_log_buf, 0x00, sizeof(sb_log_buf));
	switch(log_level) {
		case DEBUG:
			memcpy(sb_log_buf, debug_str, 7);
			len = 7;
			break;
		case INFO:
			memcpy(sb_log_buf, info_str, 7);
			len = 6;
			break;
		case ERROR:
			memcpy(sb_log_buf, error_str, 7);
			len = 7;
			break;
		case FATAL:
			memcpy(sb_log_buf, fatal_str, 7);
			len = 7;
			break;
		case SYSTEM:
			memcpy(sb_log_buf, system_str, 8);
			len = 8;
			break;
	}
	if (cache_str_time[0] != '\0') {
		memcpy(sb_log_buf + len, cache_str_time, cache_str_time_len - 1);
		len += cache_str_time_len - 1;
	}

	sprintf(sb_log_buf + len, " %s:%d| ", filename, lineno);
}


/********** tcp sockopt start************/
int sb_set_reuseaddr(int s, int onoff)
{
	if (onoff)
		onoff = 1;

	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void *) &onoff, sizeof (int)) == -1) {
		return SB_ERROR;
	}

	return SB_OK;
}

int sb_set_tcppush(int s, int onoff)
{
	if (onoff)
		onoff = 1;

	return setsockopt(s, IPPROTO_TCP, TCP_CORK, (const void *) &onoff, sizeof(int));
}

int sb_set_nonblocking(int s)
{
	int     opts;

	opts = fcntl( s, F_GETFL ) ;
	if( opts < 0 )
		return SB_ERROR;

	opts = opts | O_NONBLOCK;
	if( fcntl( s, F_SETFL , opts ) < 0 ) {
		return SB_ERROR;
	}

	return SB_OK;
}

int set_keepalive_params(int sockfd)
{
	int keep_idle = 6;   // 如果在60秒内没有任何数据交互,则进行探测. 缺省值:7200(s)  
	int keep_interval = 5;   // 探测时发探测包的时间间隔为5秒. 缺省值:75(s)  
	int keep_count = 2;   // 探测重试的次数. 全部超时则认定连接失效.缺省值:9(次) 

	if (setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &keep_idle, sizeof(int)) < 0) {
		return SB_ERROR;
	}

	if (setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &keep_interval, sizeof(int)) < 0) {
		return SB_ERROR;
	}

	if (setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &keep_count, sizeof(int)) < 0) {
		return SB_ERROR;
	}

	return SB_OK;
}

int sb_set_tcpkeepalive(int s)
{
	int on = 1; 

	if (set_keepalive_params(s)) {
		return SB_ERROR;
	}       

	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(int)) ) {
		return SB_ERROR;
	}       

	return SB_OK;
}
/********** tcp sockopt end************/


/********* get cpu num info *************/
/* @RETURN  -1 : error 	cpu_num: */
int sb_get_cpu_num()
{
	FILE *file;
	char line_buf[1024];
	int cpu_num = 0;

	if ((file = fopen("/proc/cpuinfo", "r")) == NULL) {
		return -1;
	}

	while (!feof(file)) {
		if (fgets(line_buf, sizeof(line_buf) - 1, file) == NULL) {
			break;
		}

		if (strstr(line_buf, "processor") != NULL) {
			cpu_num++;
		}
	}

	return cpu_num;
}

extern char **sb_environ;
extern char **sb_argv;
extern int sb_argc;
extern char *sb_last_env;
extern char **sb_os_argv;
extern int sb_argv_max_size;	/* contain the last \0 */
extern char **environ;
int sb_save_argv_env(int argc, char **argv)
{
	int i = 0;
	int env_cnt= 0;
	int size = 0;

	sb_os_argv = argv;
	sb_argv_max_size = 0;

	while (environ[i]) {
		i++;
	}
	env_cnt = i;

	sb_environ = (char**)malloc(sizeof(char *) * env_cnt);
	sb_argc = argc;
	sb_argv = (char**)malloc(sizeof(char *) * argc);
	sb_last_env = environ[env_cnt - 1] + strlen(environ[env_cnt - 1]);

	for (i = 0; i < argc; i++){
		size = strlen(argv[i]) + 1; 
		sb_argv[i] = (char*)malloc(size);
		if (sb_argv[i] == NULL) {
			return -1;
		}
		memcpy(sb_argv[i], argv[i], size);
	}
				
	for (i = 0; i < env_cnt; i++) {
		size = strlen(environ[i]) + 1; 
		sb_environ[i] = (char*)malloc(size);
		if (sb_environ[i] == NULL) {
			return -1;
		}
		memcpy(sb_environ[i], environ[i], size);
	}

	sb_argv_max_size = sb_last_env - argv[0] + 1;

	return 0;
}

int sb_setproctitle(char *title)
{
	int size;
	int len = strlen(title);

	size = sb_argv_max_size < len ? sb_argv_max_size : len;
	sb_os_argv[1] = NULL;

	memcpy(sb_os_argv[0], title, size);
	sb_os_argv[0][size] = '\0';
	memset(sb_os_argv[0] + size, 0x00, sb_argv_max_size - size);

	prctl(PR_SET_NAME, title, 0, 0, 0);

	return 0;
}

void sb_set_process_title(char *fmt,...)
{
        char title[400];
        va_list ap;

        memset(title, 0x00, sizeof(title));

        va_start(ap, fmt);
        vsprintf(title, fmt, ap);
        sb_setproctitle(title);
        va_end(ap);
}

int sb_setaffinity(int index)
{
	cpu_set_t set;

	CPU_ZERO(&set);	
	CPU_SET(index % sb_ncpu, &set);	
	
	return sched_setaffinity(0, sizeof(set), &set);
}

void sb_update_time(struct timeval *cache_time,  char *cache_str_time)
{
        time_t sec;
        struct tm *tmp_tm = NULL;

        gettimeofday(cache_time, NULL);
        sec = cache_time->tv_sec;

        int tm_sec;         /* seconds */
        int tm_min;         /* minutes */
        int tm_hour;        /* hours */

        tmp_tm = localtime(&sec);
        tmp_tm->tm_year += 1900;
        tmp_tm->tm_mon++;

        sprintf(cache_str_time, "%04d-%02d-%02d %02d:%02d:%02d", tmp_tm->tm_year, tmp_tm->tm_mon, tmp_tm->tm_mday,
                        tmp_tm->tm_hour, tmp_tm->tm_min, tmp_tm->tm_sec );
}

int sb_cmp_sockaddr(struct sockaddr *sa, struct sockaddr *sb)
{
	struct sockaddr_in *sa4, *sb4;
	struct sockaddr_in6 *sa6, *sb6;
	struct sockaddr_un *saun, *sbun;

	if (sa->sa_family != sb->sa_family)
		return SB_ERROR;

	
	switch (sa->sa_family) {
		case AF_INET6:
			sa6 = (struct sockaddr_in6*)sa;
			sb6 = (struct sockaddr_in6*)sb;
			if (sa6->sin6_port != sb6->sin6_port || memcmp(&sa6->sin6_addr, &sb6->sin6_addr, 16)) {
				return SB_ERROR; 
			}
			break;	
		case AF_UNIX:
			#if 0
			saun = (struct sockaddr_un*)saun;
			sbun = (struct sockaddr_un*)sbun;
			if (memcmp(&saun->sun_path, &sbun->sun_path, sizeof(saun->sun_path)) != 0) {
				return SB_ERROR;
			}
			#endif
			break;
		default:
			sa4 = (struct sockaddr_in*)sa;
			sb4 = (struct sockaddr_in*)sb;
			if (sa4->sin_port != sb4->sin_port || sa4->sin_addr.s_addr != sb4->sin_addr.s_addr) {
				return SB_ERROR; 
			}
			break;
	}

	return SB_OK;
}
